package com.freeleaps.devops

import com.freeleaps.devops.enums.ImageBuilderTypes

class ImageBuilder {
  def steps
  def workspace
  def contextRoot
  def dockerfile
  def builderType

  // customized parameters
  def name
  def registry
  def repository
  def architectures
  def version
  def registryCredentialsId

  def buildxBuilderName

  ImageBuilder(steps, workspace, contextRoot, dockerfile, builderType) {
    this.steps = steps
    this.workspace = workspace
    this.contextRoot = contextRoot
    this.dockerfile = dockerfile
    this.builderType = builderType
  }

  def setManifestsOfImage(registry, repository, name, version) {
    if (registry == null || registry.isEmpty()) {
      steps.error("registry is empty")
    }
    this.registry = registry

    if (repository == null || repository.isEmpty()) {
      steps.error("repository is empty")
    }
    this.repository = repository

    if (name == null || name.isEmpty()) {
      steps.error("name is empty")
    }
    this.name = name

    if (version == null || version.isEmpty()) {
      steps.error("version is empty")
    }
    this.version = version
  }

  def setArchitectures(architectures) {
    if (architectures == null || architectures.isEmpty()) {
      steps.error("architectures is empty")
    }
    this.architectures = architectures

    if (builderType == ImageBuilderTypes.DOCKER_IN_DOCKER && architectures.size() > 1) {
      steps.log.warn("ImageBuilder", "If you want to build multi-arch images and using Docker in Docker (DIND) as builder, system will using buildx to replace build command.")
      steps.log.info("ImageBuilder", "Creating buildx builder with name: multiarch-builder-${name}")
      steps.sh "docker buildx create --use --name multiarch-builder-${name} --platform ${architectures.join(",")} --driver-opt network=host"
      steps.log.info("ImageBuilder", "Inspecting buildx builder with name: multiarch-builder-${name}")
      steps.sh "docker buildx inspect --bootstrap"
      this.buildxBuilderName = "multiarch-builder-${name}"
    }
  }

  def useCredentials(registryCredentialsId) {
    if (registryCredentialsId == null || registryCredentialsId.isEmpty()) {
      steps.error("registryCredentialsId is empty")
    }
    this.registryCredentialsId = registryCredentialsId
  }

  def build() {
    try {
      steps.log.info("ImageBuilder", "Building image with ${builderType.builder}")
      steps.log.info("ImageBuilder", "Workspace sets to: ${workspace}")
      steps.log.info("ImageBuilder", "Using dockerfile at: ${dockerfile}, context root sets to: ${contextRoot}")

      if (architectures == null || architectures.isEmpty()) {
        steps.log.warn("ImageBuilder", "No architectures specified, using default amd64")
        architectures = ['linux/amd64']
      }

      steps.withCredentials([steps.usernamePassword(credentialsId: registryCredentialsId, passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
        steps.log.info("ImageBuilder", "Authentication to ${registry}")
        switch(builderType) {
          case ImageBuilderTypes.DOCKER_IN_DOCKER:
            steps.sh "docker login -u ${steps.env.DOCKER_USERNAME} -p ${steps.env.DOCKER_PASSWORD} ${registry}"
            break
          case ImageBuilderTypes.KANIKO:
            def auth = "${steps.env.DOCKER_USERNAME}:${steps.env.DOCKER_PASSWORD}".bytes.encodeBase64().toString()
            steps.writeFile file: '/kaniko/.docker/config.json', text: """{
              "auths": {
                "${registry}": {
                  "auth": "${auth}"
                }
              }
            }"""
            break
          default:
            steps.error("Unsupported builder type: ${builderType.builder}")
        }
      }

      switch(builderType) {
        case ImageBuilderTypes.DOCKER_IN_DOCKER:
          steps.dir(workspace) {
            if (buildxBuilderName != null && !buildxBuilderName.isEmpty() && architectures.size() > 1) {
              steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures} using buildx builder: ${buildxBuilderName}, tag sets to ${version}")
              def currentPath = steps.sh(script: "pwd", returnStdout: true).trim()
              steps.log.info("ImageBuilder", "Current working dir: ${currentPath}")
              def filesInCurrentDir = steps.sh(script: "ls -la", returnStdout: true).trim()
              steps.log.info("ImageBuilder", "Files in current dir: ${filesInCurrentDir}")
              def filesInContext = steps.sh(script: "ls -la ${contextRoot}", returnStdout: true).trim()
              steps.log.info("ImageBuilder", "Files in build context: ${filesInContext}")
              steps.log.info("ImageBuilder", "Set builder log level to plain...")
              steps.env.BUILDKIT_PROGRESS = "plain"
              steps.log.info("ImageBuilder", "Set builder timeout to 90min...")
              steps.env.BUILDKIT_TIMEOUT = "5400s"
              steps.sh "docker buildx build --builder ${buildxBuilderName} --no-cache --platform ${architectures.join(",")} -t ${registry}/${repository}/${name}:${version} -f ${dockerfile} --push ${contextRoot}"
              steps.env.BUILD_IMAGE_REGISTRY = "${registry}"
              steps.env.BUILD_IMAGE_REPO = "${repository}"
              steps.env.BUILD_IMAGE_NAME = "${name}"
              steps.env.BUILD_IMAGE_VERSION = "${version}"
            } else {
              architectures.each { architecture ->
                def archTag = architecture.split("/")[1]
                steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}")
                steps.sh "docker build -t ${registry}/${repository}/${name}:${version} --platform ${architecture} -f ${dockerfile} ${contextRoot}"
                steps.sh "docker push ${registry}/${repository}/${name}:${version}"
              }
              steps.env.BUILD_IMAGE_REGISTRY = "${registry}"
              steps.env.BUILD_IMAGE_REPO = "${repository}"
              steps.env.BUILD_IMAGE_NAME = "${name}"
              steps.env.BUILD_IMAGE_VERSION = "${version}"
            }
          }
          break
        case ImageBuilderTypes.KANIKO:
          steps.dir(workspace) {
            architectures.each { architecture ->
              def archTag = architecture.split("/")[1]
              steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}")
              steps.sh "/kaniko/executor --log-format text --context ${contextRoot} --dockerfile ${dockerfile} --destination ${registry}/${repository}/${name}:${version} --custom-platform ${architecture}"
            }
            steps.env.BUILD_IMAGE_REGISTRY = "${registry}"
            steps.env.BUILD_IMAGE_REPO = "${repository}"
            steps.env.BUILD_IMAGE_NAME = "${name}"
            steps.env.BUILD_IMAGE_VERSION = "${version}"
          }
          break
        default:
          steps.error("Unsupported builder type: ${builderType.builder}")
      }
    } catch (Exception e) {
      steps.log.error("ImageBuilder", "Failed to build image: ${e.message}")
      throw e
    } finally {
      if (buildxBuilderName != null && !buildxBuilderName.isEmpty() && architectures.size() > 1) {
        try {
          steps.log.info("ImageBuilder", "Cleaning up buildx builder: ${buildxBuilderName}")
          steps.sh "docker buildx rm ${buildxBuilderName} || true"
        } catch (Exception e) {
          steps.log.warn("ImageBuilder", "Failed to cleanup buildx builder: ${e.message}")
        }
      }
    }
  }
}

